# -*-coding:Utf-8 -* 
""" Mod loader module
"""
import bge, os, pickle, types, threading, importlib
from . import AUTORELOAD
from .parser import xakDocument
from .item import *

class Dependencies(object):
	
	def __init__(self, mainModId):
		self.mainID = mainModId
		self.pre_list = []
		self.post_list = []
		self.pre_needed = {}
		self.post_needed = {}
		
	def add(self,mID, dep):
		if 'core' in dep:
			dep.remove('core')
		if mID == self.mainID:
			return
		if len(dep) == 0:
			self.addPreMod(mID)
		elif len(dep) == 1 and self.mainID in dep:
			self.addPostMod(mID)
		elif self.mainID in dep:
			dep.remove(self.mainID)
			for aBase in tuple(dep):
				if aBase in self.pre_list or aBase in self.post_list:
					dep.remove(aBase)
			if dep == []:
				self.addPostMod(mID)
			else:
				self.post_needed[mID] = dep
		else:
			for aBase in tuple(dep):
				if aBase in self.pre_list:
					dep.remove(aBase)
			if dep == []:
				self.addPreMod(mID)
			else:
				self.pre_needed[mID] = dep	
				
	def addPostMod(self,mID):
		self.post_list.append(mID)
		for aMod in tuple(self.post_needed.keys()):
			if self.post_needed[aMod] == [mID]:
				del self.post_needed[aMod]
				self.addPostMod(aMod)
			elif mID in self.post_needed[aMod]:
				self.post_needed[aMod].remove(mID)
	def addPreMod(self,mID):
		self.pre_list.append(mID)
		for aMod in tuple(self.pre_needed.keys()):
			if self.pre_needed[aMod] == [mID]:
				del self.pre_needed[aMod]
				self.addPreMod(aMod)
			elif mID in self.pre_needed[aMod]:
				self.pre_needed[aMod].remove(mID)
		for aMod in tuple(self.post_needed.keys()):
			if self.post_needed[aMod] == [mID]:
				del self.post_needed[aMod]
				self.addPostMod(aMod)
			elif mID in self.post_needed[aMod]:
				self.post_needed[aMod].remove(mID)
		

class modLoader(threading.Thread):
	
	version = 0.001
	
	def __init__(self, mainMod):
		threading.Thread.__init__(self)
		self.structure = {}
		self._main_mod = mainMod
		self._items = []
		
	def __bool__(self):
		if self.is_alive():
			return False
		if self.structure == {}:
			return False
		return True
		
	def analyseMod(self, mID, h, b):
		mod = {}
		mod['invalid'] = False
		mod['name'] = h.child("name").childs[0]
		if mod['name'] == None:
			mod['name'] = mID
		else:
			mod['name'] = mod['name'].strip()
		mod['base'] = h.child("base").childs[0]
		if mod['base'] == None:
			mod['base'] = []
		else:
			bases = []
			for aBase in mod['base'].split(','):
				bases.append(aBase.strip())
			mod['base'] = bases
			del bases
		if mID == self._main_mod:
			mod['state'] = h.child("state").childs[0]
			if mod['state'] == None:
				del mod['state']
			else:
				mod['state'] = mod['state'].strip()
		packages = {}
		for aPack in b.child("package", True, default=[]):
			if 'path' in aPack and len(aPack.childs) > 0:
				packages[aPack.childs[0].strip()] = aPack['path'].value.strip('/')
		if packages:
			mod['packages'] = packages
		libraries = {}
		for aLib in b.child("library", True, default=[]):
			if 'path' in aLib and len(aLib.childs) > 0:
				libraries[aLib.childs[0].strip()] = (aLib.getAttr('path'),aLib.getAttr('type',"Scene"))
		if libraries:
			mod['libraries'] = libraries
			
		keymap = b.child("keymap")
		if keymap != None:
			keys = {}
			for aKey in keymap.child("key", True, default=[]):
				if 'name' in aKey and len(aKey.childs) > 0:
					keys[aKey['name'].value] = (aKey.getAttr('default'),aKey.childs[0].strip())
			if keys:
				mod['keymap'] = keys
		itemFiles = []
		for anItem in b.child("items",True,default=[]):
			if 'path' in anItem:
				itemFiles.append(anItem['path'].value)
		if itemFiles:
			mod['items'] = itemFiles
		mapFiles = []
		for aMap in b.child("maps",True,default=[]):
			if 'path' in aMap:
				mapFiles.append(aMap['path'].value)
		if mapFiles:
			mod['maps'] = mapFiles
		if len(mod) == 3:
			mod['invalid'] = True
		return mod
		
	def createGameStructure(self, modList):
		
		structure = {}
		mods = {}
		validModList = []
		for aMod in modList:
			mod = xakDocument(bge.logic.expandPath(modList[aMod][0]))
			mod.build()
			if not mod.invalid:
				h = mod.getHead()
				b = mod.getBody()
				cursor = h.child("id")
				if cursor != None:
					mID = cursor.childs[0].strip()
					mods[mID] = self.analyseMod(mID, h, b)
					if not mods[mID]['invalid']:
						validModList.append(mID)
					mods[mID]['path'] = str(modList[aMod][1])
					mods[mID]['modFile'] = str(modList[aMod][0])
				mod._clean()
				del h
				del b
			else:
				print("Mod ",aMod," is invalid because : ",mod.getError())
				
		validModList = tuple(validModList)
		if validModList == () or not self._main_mod in validModList:
			return {}
		main = self._main_mod
		modDep = Dependencies(main)
		for aMod in validModList:
			modDep.add(aMod,list(mods[aMod]['base']))
		del validModList
		preMain = modDep.pre_list
		postMain = modDep.post_list
		for aMod in modDep.pre_needed:
			mods[aMod]['invalid'] = True
		for aMod in modDep.post_needed:
			mods[aMod]['invalid'] = True
		del modDep
		
		if 'state' in mods[main]:
			structure['state'] = mods[main]['state']
			del mods[main]['state']
		keys = {}
		for aMod in [main]+preMain+postMain:
			if 'keymap' in mods[aMod]:
				for aKey in  mods[aMod]['keymap']:
					if not aKey in keys:
						keys[aKey] = [0,'']
					if mods[aMod]['keymap'][aKey][0] != None:
						keys[aKey][0] = mods[aMod]['keymap'][aKey][0]
					if mods[aMod]['keymap'][aKey][1]:
						keys[aKey][1] = mods[aMod]['keymap'][aKey][1]
				del mods[aMod]['keymap']
		structure['keymap'] = keys
		structure['active_mods'] = []
		for aMod in preMain+[main]+postMain:
			mods[aMod]['id'] = aMod
			del mods[aMod]['invalid']
			del mods[aMod]['base']
			structure['active_mods'].append(mods[aMod])
			del mods[aMod]
		structure['inactive_mods'] = []
		for aMod in tuple(mods.keys()):
			structure['inactive_mods'].append({'id':aMod,'name':mods[aMod]['name']})
		del mods
		return structure
		
	def loadGameStructure(self):
		fileModList = ()
		try:
			modListFile = open(bge.logic.expandPath('//config/modlist'),"rb")
			fileModList = pickle.load(modListFile)
			modListFile.close()
		except:
			print("no modlist find in settings directory")
			
		if not os.path.isdir(bge.logic.expandPath('//mods')):
			os.mkdir(bge.logic.expandPath('//mods'))
		modInDir = os.listdir(bge.logic.expandPath('//mods'))
		actualModList = { modLoader.version:True }
		for aMod in modInDir:
			modName = os.path.splitext(aMod)[0]
			print(modName)
			if modName:
				if os.path.isfile(bge.logic.expandPath('//mods/'+aMod)):
					actualModList[modName] = ('//mods/'+aMod,'//')
				elif os.path.isfile(bge.logic.expandPath('//mods/'+aMod+'/index.xak')):
					actualModList[modName] = ('//mods/'+aMod+'/index.xak','//mods/'+aMod+'/')
		
		upTime = 0
		if os.path.lexists(bge.logic.expandPath('//config/gamestructure')):
			upTime = os.stat(bge.logic.expandPath('//config/gamestructure')).st_mtime
		noNew = True
		if AUTORELOAD:
			noNew = False
		else:
			for name in actualModList:
				modTime = os.stat(bge.logic.expandPath(actualModList[name][0])).st_mtime
				if not name in fileModList or upTime<modTime:
					noNew = False
		
		gameStructure = {}
		
		if noNew:
			try:
				gameStructureFile = open(bge.logic.expandPath('//config/gamestructure'),"rb")
				gameStructure = pickle.load(gameStructureFile)
				gameStructureFile.close()
			except:
				noNew = False
				
		if not noNew:
			try:
				modListFile = open(bge.logic.expandPath('//config/modlist'),"wb")
				pickle.dump(tuple(actualModList.keys()), modListFile)
				modListFile.close()
			except:
				print("modlist not saved")
		else:
			return gameStructure
			
		del actualModList[modLoader.version]
		
		gameStructure = self.createGameStructure(actualModList)
		try:
			gameStructureFile = open(bge.logic.expandPath('//config/gamestructure'),"wb")
		except:
			print("/!\\ Structure not saved /!\\")
		else:
			pickle.dump(gameStructure,gameStructureFile)
			gameStructureFile.close()
		return gameStructure
		
	def run(self):
		self.structure = self.loadGameStructure()
		if self.structure == {}:
			return
		self.structure['step'] = 1.0/(len(self.structure['active_mods'])+1)
		#self.structure['scripts'] = []
		self.structure['error_scripts'] = []
		self.structure['items'] = []
		self.structure['hideBlendMap'] = []
		self.structure['blendMap'] = []
		self.structure['maps'] = {}
		self.structure['libraries'] = {}
	
	def getErrorScripts(self):
		if 'error_scripts' in self.structure:
			return self.structure['error_scripts']
		return ()
		
	def getState(self):
		if 'state' in self.structure:
			return self.structure['state']
		return 0
	
	def packageLoading(self,actual_mod):
		for aPackage in actual_mod['packages']:
			script = actual_mod['packages'][aPackage]
			path = (actual_mod['path']+script).strip('/').split('/')
			path[-1] = os.path.splitext(path[-1])[0]
			name = '.'.join(path)
			if not os.path.lexists(bge.logic.expandPath(actual_mod['path']+script)):
				self.structure['error_scripts'].append(aPackage)
			else:
				"""print(bge.logic.expandPath(actual_mod['path']+script))
				print(name)"""
				"""importlib.import_module(name)"""
				try:
					importlib.import_module(name)
				except Exception as e:
					self.structure['error_scripts'].append(aPackage)
					print('On script ',name)
					print(e)
		
	def mapLoading(self, *maps):
		xakMaps = []
		blendMaps = []
		for path in maps:
			ext = os.path.splitext(path)[1]
			if ext in ('.xak','.szl'):
				xakMaps.append(path)
			elif ext == '.blend':
				blendMaps.append(path)
		self.structure['blendMap'] += blendMaps
		if not xakMaps:
			return
		#for aMap in xakMaps:
			
		
	def initMods(self,cursor,glob):
		if self.structure == {} or cursor >= len(self.structure['active_mods']):
			return 1.0
		actual_mod = self.structure['active_mods'][cursor]
		glob['PATH'] = actual_mod['path']
		glob['MOD_ID'] = actual_mod['id']
		
		if 'packages' in actual_mod:
			self.packageLoading(actual_mod)
					
		if 'libraries' in actual_mod:
			for aLib in actual_mod['libraries']:
				path = bge.logic.expandPath(actual_mod['path']+actual_mod['libraries'][aLib][0])
				ty = actual_mod['libraries'][aLib][1]
				if not os.path.lexists(path) or \
				 ty not in ("Scene","Mesh","Action") or \
				 os.path.splitext(path)[1] != '.blend':
					continue
				self.structure['libraries'][aLib] = (path,ty)
		
		if 'items' in actual_mod:
			for anItem in actual_mod['items']:
				anItem = actual_mod['path']+anItem
				path = bge.logic.expandPath(anItem)
				if not os.path.lexists(path):
					continue
				if os.path.isdir(path):
					self.structure['items'] += \
					[anItem+'/'+p for p in os.listdir(path) if os.path.splitext(p)[1] == '.xak']
				else:
					self.structure['items'].append(anItem)
			del actual_mod['items']
		
		if 'maps' in actual_mod:
			for aMap in actual_mod['maps']:
				path = bge.logic.expandPath(actual_mod['path']+aMap)
				if not os.path.lexists(path):
					continue
				if os.path.isdir(path):
					self.mapLoading(*(path+'/'+p for p in os.listdir(path)))
				else:
					self.mapLoading(path)
		return cursor*self.structure['step']
		
	def getLibs(self):
		if 'libraries' in self.structure:
			return self.structure['libraries']
		return {}
	
	def loadMaps(self):
		for aMap in self.structure['blendMap']:
			name = os.path.splitext(os.path.basename(aMap))[0]
			if name in self.structure['maps']:
				continue
			try:
				mapFile = open(bge.logic.expandPath('//config/maps/'+name),"wb")
			except OSError:
				continue
			pickle.dump([aMap],mapFile)
			pickle.dump({},mapFile)
			pickle.dump({},mapFile)
			mapFile.close()
			self.structure['maps'][name] = bge.logic.expandPath('//config/maps/'+name)
		return self.structure['maps']
			
	def loadItems(self):
		for anItem in self.structure['items']:
			item_loaded = ItemLoader(bge.logic.expandPath(anItem))
			item_loaded.load()
